/*
 * This file is part of magnolia-module-bespin.
 * 
 * magnolia-module-bespin is free software: you
 * can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 * 
 * magnolia-module-bespin is distributed in the hope that it will be
 * useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU General Public
 * License for more details.
 * 
 * You should have received a copy of the GNU General
 * Public License along with magnolia-module-bespin.  If not, see
 * <http://www.gnu.org/licenses/>.
 */

"define metadata";
({
    "description": "Freemarker syntax highlighter",
    "dependencies": {
        "syntax_manager": "0.0"
    },
    "provides": [
        {
            "ep": "fileextension",
            "name": "ftl",
            "syntax": "freemarker"
        },
        {
            "ep": "syntax",
            "name": "freemarker",
            "pointer": "#FreemarkerSyntax"
        }
    ]
});
"end";

var SC = require('sproutcore/runtime').SC;
var Promise = require('bespin:promise').Promise;
var StandardSyntax = require('syntax_manager:controllers/standardsyntax').
    StandardSyntax;

var freemarkerStates = {
    // 10.2.4.1 Data state
    start: [
        {
            regex:  /^<!--/,
            tag:    'comment',
            then:   'commentStart'
        },
        {
            regex:  /^<!/,
            tag:    'directive',
            then:   'markupDeclarationOpen'
        },
        {
            regex:  /^<\?/,
            tag:    'comment',
            then:   'bogusComment'
        },
        {
            regex:  /^</,
            tag:    'operator',
            then:   'tagOpen'
        },
        {
            regex:  /^\[#/,
            tag:    'operator',
            then:   'freemarkerTag'
        },
        {
            regex:  /^\[\/#/,
            tag:    'operator',
            then:   'freemarkerTag'
        },
        {
            regex:  /^\[@/,
            tag:    'operator',
            then:   'freemarkerTag'
        },
        {
            regex:  /^\[\/@/,
            tag:    'operator',
            then:   'freemarkerTag'
        },
        {
            regex:  /^[^<\[]+/,
            tag:    'plain'
        }
    ],

    freemarkerTag: [
        {
            regex:  /^(?:list|if|else|macro|import|include|switch|case|break|assign|local|global|nested|recurse|fallback|visit|function|return|t|rt|lt|nt|ftl)(?![a-zA-Z0-9_])/,
            tag:    'keyword'
        },
        {
            regex:  /^[A-Za-z0-9_]+/,
            tag:    'identifier'
        },
        {
            regex:  /^[=!]/,
            tag:    'operator'
        },
        {
            regex: /^\[/,
            tag:   'operator',
            then:  'bracketInTag'
        },
        {
            regex: /^\]/,
            tag:   'operator',
            then:  'start'
        },
        {
            regex: /^\/\]/,
            tag:   'operator',
            then:  'start'
        },
        {
            regex:  /^'/,
            tag:    'string',
            then:   'qstring'
        },
        {
            regex:  /^"/,
            tag:    'string',
            then:   'qqstring'
        },
        {
            regex: /^./,
            tag:   'plain'
        }
    ],

    qstring: [
        {
            regex:  /^'/,
            tag:    'string',
            then:   'freemarkerTag'
        },
        {
            regex:  /^(?:\\.|[^'\\])+/,
            tag:    'string'
        }
    ],

    qqstring: [
        {
            regex:  /^"/,
            tag:    'string',
            then:   'freemarkerTag'
        },
        {
            regex:  /^(?:\\.|[^"\\])+/,
            tag:    'string'
        }
    ],


    bqstring: [
        {
            regex:  /^'/,
            tag:    'string',
            then:   'bracketInTag'
        },
        {
            regex:  /^(?:\\.|[^'\\])+/,
            tag:    'string'
        }
    ],

    bqqstring: [
        {
            regex:  /^"/,
            tag:    'string',
            then:   'bracketInTag'
        },
        {
            regex:  /^(?:\\.|[^"\\])+/,
            tag:    'string'
        }
    ],

    bracketInTag:  [
        {
            regex: /^\]/,
            tag:   'operator',
            then:  'freemarkerTag'
        },
        {
            regex:  /^'/,
            tag:    'string',
            then:   'bqstring'
        },
        {
            regex:  /^"/,
            tag:    'string',
            then:   'bqqstring'
        },
        {
            regex:  /^./,
            tag:    'plain'
        }
    ],

    // 10.2.4.8 Tag open state
    tagOpen: [
        {
            regex:  /^\//,
            tag:    'operator',
            then:   'endTagOpen'
        },
        {
            regex:  /^script/i,
            tag:    'keyword',
            then:   'script_beforeAttrName'
        },
        {
            regex:  /^[a-zA-Z]/,
            tag:    'keyword',
            then:   'tagName'
        },
        {
            regex:  /^(?=.)/,
            tag:    'plain',
            then:   'start'
        }
    ],

    // 10.2.4.6 Script data state
    scriptData: [
        {
            regex:  /^<(?=\/script>)/i,
            tag:    'operator',
            then:   'tagOpen stop:js'
        },
        {
            regex:  /^[^<]+/,
            tag:    'plain'
        }
    ],

    // 10.2.4.9 End tag open state
    endTagOpen: [
        {
            regex:  /^[a-zA-Z]/,
            tag:    'keyword',
            then:   'tagName'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusComment'
        }
    ],

    // 10.2.4.10 Tag name state
    tagName: [
        {
            regex:  /^\s+/,
            tag:    'plain',
            then:   'normal_beforeAttrName'
        },
        {
            regex:  /^\//,
            tag:    'operator',
            then:   'normal_selfClosingStartTag'
        },
        {
            regex:  /^>/,
            tag:    'operator',
            then:   'start'
        },
        {
            regex:  /^[^ \t\n\/>]+/,
            tag:    'keyword'
        }
    ],

    // 10.2.4.44 Bogus comment state
    bogusComment: [
        {
            regex:  /^[^>]+/,
            tag:    'comment'
        },
        {
            regex:  /^>/,
            tag:    'comment',
            then:   'start'
        }
    ],

    // 10.2.4.45 Markup declaration open state
    markupDeclarationOpen: [
        {
            regex:  /^doctype/i,
            tag:    'directive',
            then:   'doctype'
        },
        {
            regex:  /^(?=.)/,
            tag:    'comment',
            then:   'bogusComment'
        }
    ],

    // 10.2.4.46 Comment start state
    commentStart: [
        {
            regex:  /^-->/,
            tag:    'comment',
            then:   'start'
        },
        {
            regex:  /^[^-]+/,
            tag:    'comment'
        }
    ],

    // 10.2.4.53 DOCTYPE state
    doctype: [
        {
            regex:  /^\s/,
            tag:    'plain',
            then:   'beforeDoctypeName'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'beforeDoctypeName'
        }
    ],

    // 10.2.4.54 Before DOCTYPE name state
    beforeDoctypeName: [
        {
            regex:  /^\s+/,
            tag:    'plain'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^./,
            tag:    'directive',
            then:   'doctypeName'
        }
    ],

    // 10.2.4.55 DOCTYPE name state
    doctypeName: [
        {
            regex:  /^\s/,
            tag:    'plain',
            then:   'afterDoctypeName'
        },
        {
            regex:  /^>/,
            tag:    'directive',
            then:   'start'
        },
        {
            regex:  /^[^ \t\n>]+/,
            tag:    'directive'
        }
    ],

    // 10.2.4.56 After DOCTYPE name state
    afterDoctypeName: [
        {
            regex:  /^\s+/,
            tag:    'directive'
        },
        {
            regex:  /^>/,
            tag:    'directive',
            then:   'start'
        },
        {
            regex:  /^public/i,
            tag:    'directive',
            then:   'afterDoctypePublicKeyword'
        },
        {
            regex:  /^system/i,
            tag:    'directive',
            then:   'afterDoctypeSystemKeyword'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.57 After DOCTYPE public keyword state
    afterDoctypePublicKeyword: [
        {
            regex:  /^\s+/,
            tag:    'plain',
            then:   'beforeDoctypePublicId'
        },
        {
            regex:  /^"/,
            tag:    'error',
            then:   'doctypePublicIdQQ'
        },
        {
            regex:  /^'/,
            tag:    'error',
            then:   'doctypePublicIdQ'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.58 Before DOCTYPE public identifier
    beforeDoctypePublicId: [
        {
            regex:  /^\s+/,
            tag:    'plain'
        },
        {
            regex:  /^"/,
            tag:    'string',
            then:   'doctypePublicIdQQ'
        },
        {
            regex:  /^'/,
            tag:    'string',
            then:   'doctypePublicIdQ'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.59 DOCTYPE public identifier (double-quoted) state
    doctypePublicIdQQ: [
        {
            regex:  /^"/,
            tag:    'string',
            then:   'afterDoctypePublicId'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^[^>"]+/,
            tag:    'string'
        }
    ],

    // 10.2.4.60 DOCTYPE public identifier (single-quoted) state
    doctypePublicIdQ: [
        {
            regex:  /^'/,
            tag:    'string',
            then:   'afterDoctypePublicId'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^[^>']+/,
            tag:    'string'
        }
    ],

    // 10.2.4.61 After DOCTYPE public identifier state
    afterDoctypePublicId: [
        {
            regex:  /^\s/,
            tag:    'plain',
            then:   'betweenDoctypePublicAndSystemIds'
        },
        {
            regex:  /^>/,
            tag:    'directive',
            then:   'start'
        },
        {
            regex:  /^"/,
            tag:    'error',
            then:   'doctypeSystemIdQQ'
        },
        {
            regex:  /^'/,
            tag:    'error',
            then:   'doctypeSystemIdQ'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.62 Between DOCTYPE public and system identifiers state
    betweenDoctypePublicAndSystemIds: [
        {
            regex:  /^\s+/,
            tag:    'plain',
            then:   'betweenDoctypePublicAndSystemIds'
        },
        {
            regex:  /^>/,
            tag:    'directive',
            then:   'start'
        },
        {
            regex:  /^"/,
            tag:    'string',
            then:   'doctypeSystemIdQQ'
        },
        {
            regex:  /^'/,
            tag:    'string',
            then:   'doctypeSystemIdQ'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.63 After DOCTYPE system keyword state
    afterDoctypeSystemKeyword: [
        {
            regex:  /^\s/,
            tag:    'plain',
            then:   'beforeDoctypeSystemId'
        },
        {
            regex:  /^"/,
            tag:    'error',
            then:   'doctypeSystemIdQQ'
        },
        {
            regex:  /^'/,
            tag:    'error',
            then:   'doctypeSystemIdQ'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.64 Before DOCTYPE system identifier state
    beforeDoctypeSystemId: [
        {
            regex:  /^\s+/,
            tag:    'plain',
            then:   'beforeDoctypeSystemId'
        },
        {
            regex:  /^"/,
            tag:    'string',
            then:   'doctypeSystemIdQQ'
        },
        {
            regex:  /^'/,
            tag:    'string',
            then:   'doctypeSystemIdQ'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.65 DOCTYPE system identifier (double-quoted) state
    doctypeSystemIdQQ: [
        {
            regex:  /^"/,
            tag:    'string',
            then:   'afterDoctypeSystemId'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^[^">]+/,
            tag:    'string'
        }
    ],

    // 10.2.4.66 DOCTYPE system identifier (single-quoted) state
    doctypeSystemIdQ: [
        {
            regex:  /^'/,
            tag:    'string',
            then:   'afterDoctypeSystemId'
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   'start'
        },
        {
            regex:  /^[^'>]+/,
            tag:    'string'
        }
    ],

    // 10.2.4.67 After DOCTYPE system identifier state
    afterDoctypeSystemId: [
        {
            regex:  /^\s+/,
            tag:    'plain'
        },
        {
            regex:  /^>/,
            tag:    'directive',
            then:   'start'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   'bogusDoctype'
        }
    ],

    // 10.2.4.68 Bogus DOCTYPE state
    bogusDoctype: [
        {
            regex:  /^>/,
            tag:    'directive',
            then:   'start'
        },
        {
            regex:  /^[^>]+/,
            tag:    'directive'
        }
    ]
};


var createFreemarkerTagStates = function(prefix, interiorActions) {
    freemarkerStates[prefix + "_beforeAttrName"] = [
        {
            regex:  /^\s+/,
            tag:    'plain'
        },
        {
            regex:  /^\//,
            tag:    'operator',
            then:   prefix + "_selfClosingStartTag"
        },
        {
            regex:  /^>/,
            tag:    'operator',
            then:   interiorActions
        },
        {
            regex:  /^./,
            tag:    'keyword',
            then:   prefix + "_attrName"
        }
    ];

    // 10.2.4.35 Attribute name state
    freemarkerStates[prefix + "_attrName"] = [
        {
            regex:  /^\s+/,
            tag:    'plain',
            then:   prefix + "_afterAttrName"
        },
        {
            regex:  /^\//,
            tag:    'operator',
            then:   prefix + "_selfClosingStartTag"
        },
        {
            regex:  /^=/,
            tag:    'operator',
            then:   prefix + "_beforeAttrValue"
        },
        {
            regex:  /^>/,
            tag:    'operator',
            then:   interiorActions
        },
        {
            regex:  /^["'<]+/,
            tag:    'error'
        },
        {
            regex:  /^[^ \t\n\/=>"'<]+/,
            tag:    'keyword'
        }
    ];

    freemarkerStates[prefix + "_afterAttrName"] = [
        {
            regex:  /^\s+/,
            tag:    'plain'
        },
        {
            regex:  /^\//,
            tag:    'operator',
            then:   prefix + "_selfClosingStartTag"
        },
        {
            regex:  /^=/,
            tag:    'operator',
            then:   prefix + "_beforeAttrValue"
        },
        {
            regex:  /^>/,
            tag:    'operator',
            then:   interiorActions
        },
        {
            regex:  /^./,
            tag:    'keyword',
            then:   prefix + "_attrName"
        }
    ];

    freemarkerStates[prefix + "_beforeAttrValue"] = [
        {
            regex:  /^\s+/,
            tag:    'plain'
        },
        {
            regex:  /^"/,
            tag:    'string',
            then:   prefix + "_attrValueQQ"
        },
        {
            regex:  /^(?=&)/,
            tag:    'plain',
            then:   prefix + "_attrValueU"
        },
        {
            regex:  /^'/,
            tag:    'string',
            then:   prefix + "_attrValueQ"
        },
        {
            regex:  /^>/,
            tag:    'error',
            then:   interiorActions
        },
        {
            regex:  /^./,
            tag:    'string',
            then:   prefix + "_attrValueU"
        }
    ];

    freemarkerStates[prefix + "_attrValueQQ"] = [
        {
            regex:  /^"/,
            tag:    'string',
            then:   prefix + "_afterAttrValueQ"
        },
        {
            regex:  /^[^"]+/,
            tag:    'string'
        }
    ];

    freemarkerStates[prefix + "_attrValueQ"] = [
        {
            regex:  /^'/,
            tag:    'string',
            then:   prefix + "_afterAttrValueQ"
        },
        {
            regex:  /^[^']+/,
            tag:    'string'
        }
    ];

    freemarkerStates[prefix + "_attrValueU"] = [
        {
            regex:  /^\s/,
            tag:    'string',
            then:   prefix + "_beforeAttrName"
        },
        {
            regex:  /^>/,
            tag:    'operator',
            then:   interiorActions
        },
        {
            regex:  /[^ \t\n>]+/,
            tag:    'string'
        }
    ];

    freemarkerStates[prefix + "_afterAttrValueQ"] = [
        {
            regex:  /^\s/,
            tag:    'plain',
            then:   prefix + "_beforeAttrName"
        },
        {
            regex:  /^\//,
            tag:    'operator',
            then:   prefix + "_selfClosingStartTag"
        },
        {
            regex:  /^>/,
            tag:    'operator',
            then:   interiorActions
        },
        {
            regex:  /^(?=.)/,
            tag:    'operator',
            then:   prefix + "_beforeAttrName"
        }
    ];

    // 10.2.4.43 Self-closing start tag state
    freemarkerStates[prefix + "_selfClosingStartTag"] = [
        {
            regex:  /^>/,
            tag:    'operator',
            then:   'start'
        },
        {
            regex:  /^./,
            tag:    'error',
            then:   prefix + "_beforeAttrName"
        }
    ];
};

createFreemarkerTagStates("normal", 'start');
createFreemarkerTagStates("script", 'scriptData start:js');

exports.FreemarkerSyntax = StandardSyntax.create({
    states: freemarkerStates
});

